use piston_window::{types::Color, Context, G2d, Key};
use rand::{thread_rng, Rng};

use crate::{
    draw::{draw_block, draw_rectagle},
    snake::{Direction, Snake},
};

const FOOD_COLOR: Color = [0.80, 0.00, 0.00, 1.00];
const BORDER_COLOR: Color = [0.00, 0.00, 1.00, 1.00];
const GAMEOVER_COLOR: Color = [0.90, 0.00, 0.00, 0.50];
const PAUSE_COLOR: Color = [0.10, 0.10, 0.10, 1.00];

const MOVING_PERIOD: f64 = 0.1;
const RESTART_TIME: f64 = 1.0;

struct Food {
    x: i32,
    y: i32,
}

impl Food {
    fn new(x: i32, y: i32) -> Self {
        Self { x, y }
    }

    fn random(width: i32, height: i32) -> Self {
        let mut rng = thread_rng();
        let x = rng.gen_range(1..width - 1);
        let y = rng.gen_range(1..height - 1);
        Self { x, y }
    }
}

pub struct Game {
    snake: Snake,
    food_exists: bool,
    food: Food,
    width: i32,
    height: i32,
    game_over: bool,
    waiting_time: f64,
    paused: bool,
}

impl Game {
    pub fn new(width: i32, height: i32) -> Self {
        Self {
            snake: Snake::new(2, 2),
            food_exists: true,
            food: Food::new(6, 4),
            width,
            height,
            game_over: false,
            waiting_time: 0.0,
            paused: false,
        }
    }

    pub fn key_pressed(&mut self, key: Key) {
        if self.game_over {
            return;
        }

        let dir = match key {
            Key::Up | Key::W => Some(Direction::Up),
            Key::Down | Key::S => Some(Direction::Down),
            Key::Left | Key::A => Some(Direction::Left),
            Key::Right | Key::D => Some(Direction::Right),
            _ => None,
        };
        if let Key::P = key {
            self.paused = !self.paused;
            return;
        }
        if dir == Some(self.snake.head_direction().opposite()) {
            return;
        }
        self.update_snake(dir);
    }

    pub fn draw(&self, con: &Context, g: &mut G2d) {
        if self.paused {
            draw_rectagle(PAUSE_COLOR, 0, 0, self.width, self.height, con, g);
            return;
        }
        self.snake.draw(con, g);

        if self.food_exists {
            draw_block(FOOD_COLOR, self.food.x, self.food.y, con, g);
        }
        draw_rectagle(BORDER_COLOR, 0, 0, self.width, 1, con, g);
        draw_rectagle(BORDER_COLOR, 0, self.height - 1, self.width, 1, con, g);
        draw_rectagle(BORDER_COLOR, 0, 0, 1, self.height, con, g);
        draw_rectagle(BORDER_COLOR, self.width - 1, 0, 1, self.height, con, g);
        if self.game_over {
            draw_rectagle(GAMEOVER_COLOR, 0, 0, self.width, self.height, con, g);
        }
    }

    pub fn update(&mut self, delta_time: f64) {
        if self.paused {
            return;
        }
        self.waiting_time += delta_time;
        if self.game_over {
            if self.waiting_time > RESTART_TIME {
                self.restart();
            }
            return;
        }

        if !self.food_exists {
            self.add_food();
        }

        if self.waiting_time > MOVING_PERIOD {
            self.update_snake(None)
        }
    }

    fn check_eating(&mut self) {
        let (head_x, head_y) = self.snake.head_position();
        if self.food_exists && self.food.x == head_x && self.food.y == head_y {
            self.food_exists = false;
            self.snake.restore_tail();
        }
    }

    fn check_if_snake_alive(&self, dir: Option<Direction>) -> bool {
        let (next_x, next_y) = self.snake.next_head(dir);
        if self.snake.overlap_tail(next_x, next_y) {
            return false;
        }
        next_x > 0 && next_y > 0 && next_x < self.width - 1 && next_y < self.height - 1
    }

    pub fn restart(&mut self) {
        self.snake = Snake::new(2, 2);
        self.waiting_time = 0.0;
        self.food_exists = true;
        self.food = Food::new(6, 4);
        self.game_over = false;
        self.paused = false
    }

    pub fn add_food(&mut self) {
        let width = self.width;
        let height = self.height;
        let mut food = Food::random(width, height);
        while self.snake.overlap_tail(food.x, food.y) {
            food = Food::random(width, height);
        }

        self.food = food;
        self.food_exists = true;
    }

    pub fn update_snake(&mut self, dir: Option<Direction>) {
        if self.check_if_snake_alive(dir) {
            self.snake.move_forward(dir);
            self.check_eating();
        } else {
            self.game_over = true
        }
        self.waiting_time = 0.0;
    }
}
